-- 2022-8-18

---[[ 20.4.4 跟踪对表的访问

    -- 假设我们要跟踪对某个表的所有访问 由于__index和__newindex元方法都是在表中的索引不存在时才有用
    -- 因此 捕获对一个表所有访问的唯一方式是保存表是空的
    -- 如果要监控对一个表的所有访问 那么需要为真正的表创建一个代理(proxy)
    -- 这个代理是一个空的表 具有用于跟踪所有访问并将访问重定向到原来的表的合理元方法 示例20.2使用这种思修进行了实现
    
    -- 示例20.2 跟踪对表的访问
    function track(t)
        local proxy = {} -- 't'的代理表
        -- 为代理创建元素
        local mt = {
            __index = function (_,k)
                print("*access to element " .. tostring(k))
                return t[k]
            end,
            __newindex = function (_,k,v)
                print("*update of element " .. tostring(k) .. " to " .. tostring(v))
                t[k] = v  -- 更新原来的表
            end,
            __pairs = function ()
                return function (_,k) -- 迭代函数
                    local nextkey,nextvalue = next(t,k)
                    if nextkey ~= nil then -- 避免最后一个值
                        print("*traversing element " .. tostring(nextkey))
                    end
                    return nextkey,nextvalue
                end
            end,
            __len = function ()
                return #t
            end
        }
        setmetatable(proxy,mt)
        return proxy
    end

    -- 以下展示了上述代码的用法:
    --> t = {} -- 任意一个表
    --> t = track(t)
    --> t[2] = "hello"
        --> *update of element 2 to hello
    --> print(t[2])
        --> *access to element 2
        --> hello

    -- 元方法__index和__newindex按照我们设计的规则跟踪每一个访问并将其重定向到原来的表中
    -- 元方法__pairs使得我们能够像遍历原来的表一样遍历代理 从而跟踪所有的访问 最后 元方法__len通过代理实现了长度操作符:
    t = track({10,20})
    print(#t)
    for k, v in pairs(t) do
        print(k,v)
    end

    -- 如果想要同时监控几个表 并不需要为每个表创建不同的元素
    -- 相反 只要以某种形式将每个代理与其原始表映射起来 并且让所有的代理共享一个公用的元表即可
    -- 这个问题与上节所讨论的把表与其默认值关联起来的问题类似 因此可以采用相同的解决方式
    -- 例如 可以把原来的表保存在代理表的一个特殊的字段中 或者使用一个对偶表示建立代理与相应表的映射
--]]